/*
 * Copyright 2004-2014 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
 * versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 *
 * http://ec.europa.eu/idabc/eupl
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the Licence for the specific language governing permissions and limitations
 * under the Licence.
*/

package com.eviware.soapui.impl.wsdl.panels.iface;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.text.BadLocationException;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.wsdl.BindingOperation;

import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlLineNumber;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.RTextScrollPane;
import org.jdesktop.swingx.JXTable;
import org.w3c.dom.Element;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.actions.SoapUIPreferencesAction;
import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
import com.eviware.soapui.impl.support.definition.InterfaceDefinitionPart;
import com.eviware.soapui.impl.wsdl.WsdlInterface;
import com.eviware.soapui.impl.wsdl.WsdlOperation;
import com.eviware.soapui.impl.wsdl.actions.iface.CreateWsdlDocumentationAction;
import com.eviware.soapui.impl.wsdl.actions.iface.ExportDefinitionAction;
import com.eviware.soapui.impl.wsdl.actions.iface.UpdateInterfaceAction;
import com.eviware.soapui.impl.wsdl.actions.iface.tools.wsi.WSIAnalyzeAction;
import com.eviware.soapui.impl.wsdl.actions.iface.tools.wsi.WSIReportPanel;
import com.eviware.soapui.impl.wsdl.support.HelpUrls;
import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlUtils;
import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.Interface;
import com.eviware.soapui.model.support.ProjectListenerAdapter;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.action.swing.SwingActionDelegate;
import com.eviware.soapui.support.components.JEditorStatusBar;
import com.eviware.soapui.support.components.JXToolBar;
import com.eviware.soapui.support.components.MetricsPanel;
import com.eviware.soapui.support.components.MetricsPanel.MetricType;
import com.eviware.soapui.support.components.MetricsPanel.MetricsSection;
import com.eviware.soapui.support.components.ProgressDialog;
import com.eviware.soapui.support.types.StringList;
import com.eviware.soapui.support.xml.SyntaxEditorUtil;
import com.eviware.soapui.support.xml.XmlUtils;
import com.eviware.soapui.ui.support.ModelItemDesktopPanel;
import com.eviware.x.dialogs.Worker;
import com.eviware.x.dialogs.XProgressDialog;
import com.eviware.x.dialogs.XProgressMonitor;
import com.jgoodies.forms.builder.ButtonBarBuilder;

/**
 * DesktopPanel for WsdlInterface. Loads all referenced wsdls/xsds for the
 * specified WsdlInterface and displays these in seperate tabs
 *
 * @author Ole.Matzura
 */

public class WsdlInterfaceDesktopPanel extends ModelItemDesktopPanel<WsdlInterface> {
    private final static Logger logger = Logger.getLogger(WsdlInterfaceDesktopPanel.class);
    private JTabbedPane partTabs;
    private List<RSyntaxTextArea> editors = new ArrayList<RSyntaxTextArea>();
    private JTree tree;
    private Map<String, DefaultMutableTreeNode> groupNodes = new HashMap<String, DefaultMutableTreeNode>();
    private Map<String, TreePath> pathMap = new HashMap<String, TreePath>();
    private List<TreePath> navigationHistory = new ArrayList<TreePath>();
    private StringList targetNamespaces = new StringList();
    private int historyIndex;
    private boolean navigating;
    private JEditorStatusBar statusBar;
    private DefaultMutableTreeNode rootNode;
    private DefaultTreeModel treeModel;
    private InternalProjectListener projectListener;
    private final WsdlInterface iface;
    private JPanel wsiPanel;
    private WSIReportPanel reportPanel;
    private SaveWsiReportAction saveWsiReportAction;
    private MetricsPanel metrics;
    private boolean updatingInterface;
    private OperationsTableModel operationsTableModel;

    public WsdlInterfaceDesktopPanel(WsdlInterface iface) {
        super(iface);
        this.iface = iface;

        try {
            iface.getWsdlContext().loadIfNecessary();
            buildUI();
        } catch (Exception e) {
            UISupport.showErrorMessage(e);
            SwingUtilities.invokeLater(new Runnable() {

                public void run() {
                    SoapUI.getDesktop().closeDesktopPanel(WsdlInterfaceDesktopPanel.this);
                }
            });
        }
    }

    private void buildUI() {
        JTabbedPane tabs = new JTabbedPane();
        tabs.addTab("Overview", buildInterfaceOverviewTab());
        tabs.addTab("Service Endpoints", buildEndpointsTab());
        tabs.addTab("WSDL Content", buildWsdlContentTab());
        tabs.addTab("WS-I Compliance", buildWSITab());

        add(UISupport.createTabPanel(tabs, true), BorderLayout.CENTER);
    }

    private Component buildWSITab() {
        wsiPanel = new JPanel(new BorderLayout());
        wsiPanel.setBackground(Color.WHITE);
        wsiPanel.setOpaque(true);

        wsiPanel.add(builderWsiToolbar(), BorderLayout.NORTH);

        return wsiPanel;
    }

    private Component builderWsiToolbar() {
        JXToolBar toolbar = UISupport.createToolbar();

        toolbar.addFixed(UISupport.createToolbarButton(new RunWSIAction()));
        toolbar.addFixed(UISupport.createToolbarButton(new WSIOptionsAction()));
        toolbar.addRelatedGap();
        saveWsiReportAction = new SaveWsiReportAction();
        toolbar.addFixed(UISupport.createToolbarButton(saveWsiReportAction));

        toolbar.addGlue();
        toolbar.addFixed(UISupport.createToolbarButton(new ShowOnlineHelpAction(HelpUrls.WSI_COMPLIANCE_HELP_URL)));

        return toolbar;
    }

    private Component buildInterfaceOverviewTab() {
        metrics = new MetricsPanel();
        JXToolBar toolbar = UISupport.createSmallToolbar();
        toolbar.addGlue();
        toolbar.addFixed(UISupport
                .createToolbarButton(new ShowOnlineHelpAction(HelpUrls.INTERFACE_OVERVIEW_HELP_URL)));
        metrics.add(toolbar, BorderLayout.NORTH);
        MetricsSection section = metrics.addSection("WSDL Definition");

        try {
            section.addMetric("WSDL URL", MetricType.URL).set(iface.getDefinition());
            section.addMetric("Namespace").set(iface.getBindingName().getNamespaceURI());
            section.addMetric("Binding").set(iface.getBindingName().getLocalPart());
            section.addMetric("SOAP Version").set(iface.getSoapVersion().toString());
            section.addMetric("Style").set(iface.getStyle());
            section.addMetric("WS-A version").set(iface.getWsaVersion());
        } catch (Exception e) {
            UISupport.showErrorMessage(e);
        }

        section.finish();

        metrics.addSection("Definition Parts");
        section = metrics.addSection("Operations");
        operationsTableModel = new OperationsTableModel();
        JXTable table = section.addTable(operationsTableModel);
        table.getColumn(1).setPreferredWidth(60);
        table.getColumn(2).setPreferredWidth(60);
        section.finish();

        table.packColumn(3, 10);
        if (table.getColumn(3).getPreferredWidth() < 250) {
            table.getColumn(3).setPreferredWidth(250);
        }

        return new JScrollPane(metrics);
    }

    private Component buildEndpointsTab() {
        return iface.getProject().getEndpointStrategy().getConfigurationPanel(iface);
    }

    private JComponent buildWsdlContentTab() {
        partTabs = new JTabbedPane();
        partTabs.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);

        rootNode = new DefaultMutableTreeNode(iface.getName());
        treeModel = new DefaultTreeModel(rootNode);
        tree = new JTree(treeModel);
        tree.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
        tree.setExpandsSelectedPaths(true);
        tree.addTreeSelectionListener(new InternalTreeSelectionListener());
        tree.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent arg0) {
                if (arg0.getClickCount() > 1) {
                    TreePath selectionPath = tree.getSelectionPath();
                    if (selectionPath != null) {
                        DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) selectionPath.getLastPathComponent();
                        Object userObject = treeNode.getUserObject();
                        if (userObject instanceof InspectItem) {
                            InspectItem item = (InspectItem) userObject;
                            if (item != null && item.selector != null) {
                                item.selector.selectNode(item);
                            }
                        }
                    }
                }
            }
        });

        JScrollPane scrollPane = new JScrollPane(tree);
        JSplitPane split = UISupport.createHorizontalSplit(scrollPane, UISupport.createTabPanel(partTabs, true));

        split.setDividerLocation(250);
        split.setResizeWeight(0.3);

        initTreeModel(iface);

        JPanel panel = new JPanel(new BorderLayout());

        panel.add(split, BorderLayout.CENTER);
        panel.add(buildWsdlTabToolbar(), BorderLayout.PAGE_START);
        statusBar = new JEditorStatusBar();
        panel.add(statusBar, BorderLayout.PAGE_END);
        setPreferredSize(new Dimension(600, 500));

        projectListener = new InternalProjectListener();
        iface.getProject().addProjectListener(projectListener);

        return panel;
    }

    private void initTreeModel(WsdlInterface iface) {
        try {
            if (iface.getWsdlContext().loadIfNecessary()) {
                XProgressDialog progressDialog = UISupport.getDialogs().createProgressDialog("Loading Defintion", 3,
                        "Initializing definition..", true);
                Loader loader = new Loader(iface);

                if (progressDialog != null) {
                    progressDialog.setCancelLabel("Run in background");
                }

                progressDialog.run(loader);
                loader = null;
                treeModel.nodeStructureChanged(rootNode);
            }
        } catch (Exception e) {
            SoapUI.logError(e);
        }
    }

    private Component buildWsdlTabToolbar() {
        JXToolBar toolbar = UISupport.createToolbar();

        toolbar.addFixed(UISupport.createToolbarButton(new BackwardAction()));
        toolbar.addFixed(UISupport.createToolbarButton(new ForwardAction()));
        toolbar.addUnrelatedGap();
        JButton button = UISupport.createToolbarButton(SwingActionDelegate.createDelegate(
                UpdateInterfaceAction.SOAPUI_ACTION_ID, getModelItem(), null, "/updateDefinition.gif"));
        button.setText(null);
        toolbar.addFixed(button);
        button = UISupport.createToolbarButton(SwingActionDelegate.createDelegate(
                ExportDefinitionAction.SOAPUI_ACTION_ID, getModelItem(), null, "/exportDefinition.gif"));
        button.setText(null);
        toolbar.addFixed(UISupport.createToolbarButton(SwingActionDelegate.createDelegate(
                CreateWsdlDocumentationAction.SOAPUI_ACTION_ID, iface, null, "/export.gif")));
        toolbar.addFixed(button);
        toolbar.addGlue();
        button = UISupport.createToolbarButton(new ShowOnlineHelpAction(HelpUrls.WSDL_CONTENT_HELP_URL));
        button.setText(null);
        toolbar.addFixed(button);

        return toolbar;
    }

    private final class InternalProjectListener extends ProjectListenerAdapter {
        @Override
        public void interfaceUpdated(Interface iface) {
            if (iface == getModelItem()) {
                updatingInterface = true;
                partTabs.removeAll();
                tree.setSelectionRow(-1);
                rootNode.removeAllChildren();
                editors.clear();
                groupNodes.clear();
                pathMap.clear();
                targetNamespaces.clear();
                initTreeModel((WsdlInterface) iface);
                operationsTableModel.fireTableDataChanged();
                updatingInterface = false;
            }
        }
    }

    private final class InternalTreeSelectionListener implements TreeSelectionListener {
        public void valueChanged(TreeSelectionEvent e) {
            TreePath newLeadSelectionPath = e.getNewLeadSelectionPath();
            if (newLeadSelectionPath != null) {
                if (!navigating) {
                    // if we have moved back in history.. reverse before adding
                    while (historyIndex < navigationHistory.size() - 1) {
                        TreePath path = navigationHistory.remove(navigationHistory.size() - 1);
                        navigationHistory.add(historyIndex++, path);
                    }

                    navigationHistory.add(newLeadSelectionPath);
                    historyIndex = navigationHistory.size() - 1;
                }

                DefaultMutableTreeNode tn = (DefaultMutableTreeNode) newLeadSelectionPath.getLastPathComponent();
                if (tn.getUserObject() instanceof InspectItem) {
                    InspectItem item = (InspectItem) tn.getUserObject();

                    partTabs.setSelectedIndex(item.getTabIndex());
                    statusBar.setInfo(item.getDescription());

                    RSyntaxTextArea editor = editors.get(item.getTabIndex());
                    int lineNumber = item.getLineNumber();
                    try {
                        if (lineNumber > 0 && editor.getLineStartOffset(lineNumber) >= 0) {
                            editor.setCaretPosition(editor.getLineStartOffset(lineNumber));
                        } else {
                            editor.setCaretPosition(0);
                        }
                    } catch (BadLocationException e1) {
                        SoapUI.logError(e1, "Unable to reset the caret position");
                    }
                }

                tree.scrollPathToVisible(newLeadSelectionPath);
                tree.expandPath(newLeadSelectionPath);
            }
        }
    }

    private class Loader implements Worker {
        private static final String DEFINITION_PARTS_SECTION = "Definition Parts";
        private ProgressDialog progressDialog;
        private final WsdlInterface iface;
        private JProgressBar progressBar;

        public Loader(WsdlInterface iface) {
            this.iface = iface;
        }

        public Object construct(XProgressMonitor monitor) {
            MetricsSection section = metrics.getSection(DEFINITION_PARTS_SECTION);
            section.clear();

            try {
                List<InterfaceDefinitionPart> schemas = iface.getWsdlContext().getDefinitionParts();
                int tabCount = partTabs.getTabCount();

                for (InterfaceDefinitionPart part : schemas) {
                    addTab(part.getUrl(), part.getContent());
                }

                while (tabCount-- > 0) {
                    partTabs.remove(0);
                }

                return null;
            } catch (Exception e) {
                logger.error("Failed to load WSDL; " + e.getClass().getSimpleName() + "; " + e.getMessage());
                add(new JLabel("Failed to load WSDL; " + e.toString()), BorderLayout.NORTH);

                SoapUI.logError(e);

                return e;
            } finally {
                section.finish();
            }

        }

        private void addTab(String url, String content) throws Exception {
            int ix = url.startsWith("file:") ? url.lastIndexOf(File.separatorChar) : url.lastIndexOf('/');
            if (ix == -1) {
                ix = url.lastIndexOf('/');
            }

            String title = url.substring(ix + 1);

            metrics.getSection(DEFINITION_PARTS_SECTION).addMetric(title, MetricType.URL).set(url);

            if (progressBar != null) {
                progressBar.setString(title);
            } else if (progressDialog != null) {
                progressDialog.setProgress(1, title);
            }

            JPanel panel = new JPanel(new BorderLayout());
            JLabel label = new JLabel(url);
            label.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
            panel.add(label, BorderLayout.NORTH);

            RSyntaxTextArea inputArea = SyntaxEditorUtil.createDefaultXmlSyntaxTextArea();
            StringWriter writer = new StringWriter();
            // XmlUtils.serializePretty( XmlObject.Factory.parse( content ), writer
            // );
            XmlUtils.serializePretty(XmlUtils.createXmlObject(content), writer);
            String xmlString = writer.toString();

            // reparse so line numbers are correct
            // XmlObject xmlObject = XmlObject.Factory.parse( xmlString, new
            // XmlOptions().setLoadLineNumbers() );
            XmlObject xmlObject = XmlUtils.createXmlObject(xmlString, new XmlOptions().setLoadLineNumbers());

            inputArea.setText(xmlString);
            inputArea.setEditable(false);
            inputArea.setFont(UISupport.getEditorFont());

            JPanel p = new JPanel(new BorderLayout());
            RTextScrollPane scrollPane = new RTextScrollPane(inputArea);
            UISupport.addPreviewCorner(scrollPane, true);
            p.add(scrollPane, BorderLayout.CENTER);
            panel.add(p, BorderLayout.CENTER);
            partTabs.addTab(title, panel);

            if (tree != null) {
                initInspectionTree(xmlObject, inputArea);
            }
        }

        private void initInspectionTree(XmlObject xmlObject, RSyntaxTextArea inputArea) {
            DefaultMutableTreeNode treeRoot = rootNode;

            targetNamespaces.add(SchemaUtils.getTargetNamespace(xmlObject));

            int tabCount = partTabs.getTabCount() - 1;
            mapTreeItems(xmlObject, treeRoot, false, tabCount, "Complex Types",
                    "declare namespace xs='http://www.w3.org/2001/XMLSchema';//xs:complexType[@name!='']", "@name", true,
                    null);

            mapTreeItems(xmlObject, treeRoot, false, tabCount, "Simple Types",
                    "declare namespace xs='http://www.w3.org/2001/XMLSchema';//xs:simpleType[@name!='']", "@name", true,
                    null);

            mapTreeItems(xmlObject, treeRoot, false, tabCount, "Anonymous Complex Types",
                    "declare namespace xs='http://www.w3.org/2001/XMLSchema';//xs:complexType[not(exists(@name))]",
                    "parent::node()/@name", true, null);

            mapTreeItems(xmlObject, treeRoot, false, tabCount, "Global Elements",
                    "declare namespace xs='http://www.w3.org/2001/XMLSchema';//xs:schema/xs:element[@name!='']", "@name",
                    true, new GlobalElementSelector());

            mapTreeItems(xmlObject, treeRoot, false, tabCount, "Schemas",
                    "declare namespace xs='http://www.w3.org/2001/XMLSchema';//xs:schema", "@targetNamespace", true, null);

            List<DefaultMutableTreeNode> messages = mapTreeItems(xmlObject, treeRoot, false, tabCount, "Messages",
                    "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';//wsdl:message", "@name", true, null);

            for (DefaultMutableTreeNode treeNode : messages) {
                mapTreeItems(
                        ((InspectItem) treeNode.getUserObject()).item,
                        treeNode,
                        false,
                        tabCount,
                        null,
                        "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';wsdl:part",
                        "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';concat('part: name=[', @name, '] type=[', @type, '] element=[', @element, ']' )",
                        true, new PartSelector());
            }

            List<DefaultMutableTreeNode> portTypes = mapTreeItems(xmlObject, treeRoot, false, tabCount, "PortTypes",
                    "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';//wsdl:portType", "@name", true, null);

            for (DefaultMutableTreeNode treeNode : portTypes) {
                List<DefaultMutableTreeNode> operationNodes = mapTreeItems(
                        ((InspectItem) treeNode.getUserObject()).item, treeNode, false, tabCount, null,
                        "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';wsdl:operation", "@name", true, null);

                for (DefaultMutableTreeNode treeNode2 : operationNodes) {
                    mapTreeItems(((InspectItem) treeNode2.getUserObject()).item, treeNode2, false, tabCount, null,
                            "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';wsdl:*",
                            "concat( @name, ' [', local-name(), '], message=[', @message, ']' )", false, new MessageSelector());
                }
            }

            List<DefaultMutableTreeNode> bindings = mapTreeItems(
                    xmlObject,
                    treeRoot,
                    false,
                    tabCount,
                    "Bindings",
                    "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';//wsdl:binding",
                    "declare namespace wsdlsoap='http://schemas.xmlsoap.org/wsdl/soap/';concat( @name, ' [style=', wsdlsoap:binding[1]/@style, ']' )",
                    true, null);

            for (DefaultMutableTreeNode treeNode : bindings) {
                List<DefaultMutableTreeNode> operationNodes = mapTreeItems(
                        ((InspectItem) treeNode.getUserObject()).item,
                        treeNode,
                        false,
                        tabCount,
                        null,
                        "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';wsdl:operation",
                        "declare namespace wsdlsoap='http://schemas.xmlsoap.org/wsdl/soap/';concat( @name, ' [soapAction=', wsdlsoap:operation/@soapAction, ']' )",
                        true, null);

                for (DefaultMutableTreeNode treeNode2 : operationNodes) {
                    mapTreeItems(((InspectItem) treeNode2.getUserObject()).item, treeNode2, false, tabCount, null,
                            "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';wsdl:*",
                            "concat( @name, ' [', local-name(), ']' )", false, new BindingOperationSelector());
                }
            }

            List<DefaultMutableTreeNode> services = mapTreeItems(xmlObject, treeRoot, false, tabCount, "Services",
                    "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';//wsdl:service", "@name", true, null);

            for (DefaultMutableTreeNode treeNode : services) {
                mapTreeItems(((InspectItem) treeNode.getUserObject()).item, treeNode, false, tabCount, null,
                        "declare namespace wsdl='http://schemas.xmlsoap.org/wsdl/';wsdl:port",
                        "concat( 'port: name=[', @name, '] binding=[', @binding, ']' )", true, new PortSelector());
            }

            tree.expandRow(0);
            editors.add(inputArea);
        }

        public void finished() {
            if (progressDialog != null) {
                progressDialog.setVisible(false);
            }

            progressDialog = null;
        }

        public boolean onCancel() {
            progressBar = new JProgressBar(0, 1);
            progressBar.setSize(new Dimension(120, 20));
            progressBar.setStringPainted(true);
            progressBar.setString("Loading Definition..");
            progressBar.setIndeterminate(true);

            ButtonBarBuilder builder = ButtonBarBuilder.createLeftToRightBuilder();
            builder.addGlue();
            builder.addFixed(progressBar);
            builder.addGlue();
            builder.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

            partTabs.addTab("Loading.. ", builder.getPanel());
            return true;
        }
    }

    public boolean dependsOn(ModelItem modelItem) {
        return modelItem == getModelItem() || modelItem == getModelItem().getProject();
    }

    public List<DefaultMutableTreeNode> mapTreeItems(XmlObject xmlObject, DefaultMutableTreeNode treeRoot,
                                                     boolean createEmpty, int tabIndex, String groupName, String query, String nameQuery, boolean sort,
                                                     NodeSelector selector) {
        List<DefaultMutableTreeNode> resultNodes = new ArrayList<DefaultMutableTreeNode>();

        try {
            XmlObject[] items = xmlObject.selectPath(query);
            List<DefaultMutableTreeNode> treeNodes = new ArrayList<DefaultMutableTreeNode>();

            DefaultMutableTreeNode root = treeRoot;
            if (groupName != null) {
                String groupKey = new TreePath(root.getPath()).toString() + "/" + groupName;
                root = groupNodes.get(groupKey);
                if (root == null && (items.length > 0 || createEmpty)) {
                    root = new DefaultMutableTreeNode(groupName);
                    treeRoot.add(root);
                    groupNodes.put(groupKey, root);
                } else if (root != null) {
                    Enumeration<?> children = root.children();
                    while (children.hasMoreElements()) {
                        treeNodes.add((DefaultMutableTreeNode) children.nextElement());
                    }
                }
            }

            if (items.length == 0) {
                return resultNodes;
            }

            for (XmlObject item : items) {
                XmlObject[] selectPath = item.selectPath(nameQuery);
                if (selectPath.length > 0) {
                    DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(new InspectItem(item, selectPath[0],
                            tabIndex, selector));
                    treeNodes.add(treeNode);
                    resultNodes.add(treeNode);
                }
            }

            if (sort) {
                Collections.sort(treeNodes, new Comparator<DefaultMutableTreeNode>() {

                    public int compare(DefaultMutableTreeNode o1, DefaultMutableTreeNode o2) {
                        return o1.toString().compareTo(o2.toString());
                    }
                });
            }

            root.removeAllChildren();

            for (DefaultMutableTreeNode treeNode : treeNodes) {
                root.add(treeNode);

                String path = "/" + getTreeNodeName(treeNode);
                TreePath treePath = new TreePath(treeNode.getPath());
                while (treeNode.getParent() != null) {
                    treeNode = (DefaultMutableTreeNode) treeNode.getParent();
                    path = "/" + getTreeNodeName(treeNode) + path;
                }

                pathMap.put(path, treePath);
            }
        } catch (Throwable e) {
            SoapUI.log("Failed to map items for query [" + query + "]:[" + nameQuery + "]");
            SoapUI.logError(e);
        }

        return resultNodes;
    }

    private String getTreeNodeName(DefaultMutableTreeNode treeNode) {
        Object userObject = treeNode.getUserObject();
        if (userObject instanceof InspectItem) {
            return ((InspectItem) userObject).getName();
        } else {
            return treeNode.toString();
        }
    }

    private final class InspectItem {
        private final XmlObject item;
        private String name;
        private final int tabIndex;
        private XmlLineNumber lineNumber;
        private final NodeSelector selector;

        public InspectItem(XmlObject item, XmlObject nameObj, int tabIndex, NodeSelector selector) {
            this.item = item;
            this.selector = selector;
            this.name = XmlUtils.getNodeValue(nameObj.getDomNode());
            if (name == null) {
                name = nameObj.toString();
            }
            this.tabIndex = tabIndex;

            ArrayList<?> list = new ArrayList<Object>();
            XmlCursor cursor = item.newCursor();
            cursor.getAllBookmarkRefs(list);

            for (Object o : list) {
                if (o instanceof XmlLineNumber) {
                    lineNumber = (XmlLineNumber) o;
                }
            }

            cursor.dispose();
        }

        public String getDescription() {
            return getName() + "@" + targetNamespaces.get(tabIndex);
        }

        public String getName() {
            int ix = name.indexOf(' ');
            return ix == -1 ? name : name.substring(0, ix);
        }

        public int getTabIndex() {
            return tabIndex;
        }

        public int getLineNumber() {
            return lineNumber == null ? -1 : lineNumber.getLine() - 1;
        }

        @Override
        public String toString() {
            return name;
        }

        public NodeSelector getSelector() {
            return selector;
        }

        public Element getElement() {
            return (Element) item.getDomNode();
        }
    }

    public boolean onClose(boolean canCancel) {
        if (projectListener != null) {
            getModelItem().getProject().removeProjectListener(projectListener);
        }

        return release();
    }

    private void simpleSelect(InspectItem item, String attribute, String targetGroup) {
        Element elm = item.getElement();
        String type = elm.getAttribute(attribute);
        if (type.length() > 0) {
            int ix = type.indexOf(':');
            if (ix != -1) {
                type = type.substring(ix + 1);
            }

            TreePath treePath = pathMap.get("/" + getModelItem().getName() + "/" + targetGroup + "/" + type);
            if (treePath != null) {
                tree.setSelectionPath(treePath);
            }
        }
    }

    protected interface NodeSelector {
        public void selectNode(InspectItem item);
    }

    public class PartSelector implements NodeSelector {
        public void selectNode(InspectItem item) {
            Element elm = item.getElement();
            String type = elm.getAttribute("type");
            String element = elm.getAttribute("element");
            if (type.length() > 0) {
                simpleSelect(item, "type", "Complex Types");
            } else if (element.length() > 0) {
                simpleSelect(item, "element", "Global Elements");
            }
        }
    }

    public class MessageSelector implements NodeSelector {
        public void selectNode(InspectItem item) {
            simpleSelect(item, "message", "Messages");
        }
    }

    public class GlobalElementSelector implements NodeSelector {
        public void selectNode(InspectItem item) {
            simpleSelect(item, "type", "Complex Types");
        }
    }

    public class PortSelector implements NodeSelector {
        public void selectNode(InspectItem item) {
            simpleSelect(item, "binding", "Bindings");
        }
    }

    public class BindingOperationSelector implements NodeSelector {
        public void selectNode(InspectItem item) {
            Element elm = item.getElement();
            String name = elm.getAttribute("name");

            Element operationElm = (Element) elm.getParentNode();
            Element bindingElm = (Element) operationElm.getParentNode();

            String type = bindingElm.getAttribute("type");

            if (type.length() > 0) {
                int ix = type.indexOf(':');
                if (ix != -1) {
                    type = type.substring(ix + 1);
                }

                TreePath treePath = pathMap.get("/" + getModelItem().getName() + "/PortTypes/" + type + "/"
                        + operationElm.getAttribute("name") + "/" + name);
                if (treePath != null) {
                    tree.setSelectionPath(treePath);
                }
            }
        }
    }

    private class BackwardAction extends AbstractAction {
        public BackwardAction() {
            putValue(SMALL_ICON, UISupport.createImageIcon("/arrow_left.png"));
            putValue(Action.SHORT_DESCRIPTION, "Navigate to previous selection");
        }

        public void actionPerformed(ActionEvent arg0) {
            if (historyIndex > 0) {
                historyIndex--;
                navigating = true;
                tree.setSelectionPath(navigationHistory.get(historyIndex));
                navigating = false;
            }
        }
    }

    private class ForwardAction extends AbstractAction {
        public ForwardAction() {
            putValue(SMALL_ICON, UISupport.createImageIcon("/arrow_right.png"));
            putValue(Action.SHORT_DESCRIPTION, "Navigate to next selection");
        }

        public void actionPerformed(ActionEvent arg0) {
            if (historyIndex < navigationHistory.size() - 1) {
                historyIndex++;
                navigating = true;
                tree.setSelectionPath(navigationHistory.get(historyIndex));
                navigating = false;
            }

        }
    }

    private class RunWSIAction extends AbstractAction {
        public RunWSIAction() {
            putValue(SMALL_ICON, UISupport.createImageIcon("/run.gif"));
            putValue(Action.SHORT_DESCRIPTION, "Creates a WS-I report for this interface");
        }

        public void actionPerformed(ActionEvent arg0) {
            WSIAnalyzeAction action = new WSIAnalyzeAction() {
                @Override
                protected void showReport(File reportFile, String configFile) throws Exception {
                    reportPanel = new WSIReportPanel(reportFile, configFile, null, false);
                    reportPanel.setPreferredSize(new Dimension(600, 400));
                    if (wsiPanel.getComponentCount() > 1) {
                        wsiPanel.remove(1);
                    }

                    wsiPanel.add(reportPanel, BorderLayout.CENTER);
                    wsiPanel.revalidate();
                    saveWsiReportAction.setEnabled(true);
                }
            };

            action.perform(getModelItem(), null);
        }
    }

    private class WSIOptionsAction extends AbstractAction {
        public WSIOptionsAction() {
            putValue(SMALL_ICON, UISupport.createImageIcon("/options.gif"));
            putValue(Action.SHORT_DESCRIPTION, "Sets WS-I report creation options");
        }

        public void actionPerformed(ActionEvent arg0) {
            SoapUIPreferencesAction.getInstance().show(SoapUIPreferencesAction.WS_I_SETTINGS);
        }
    }

    private class SaveWsiReportAction extends AbstractAction {
        public SaveWsiReportAction() {
            putValue(SMALL_ICON, UISupport.createImageIcon("/export.gif"));
            putValue(Action.SHORT_DESCRIPTION, "Saved the current WS-I report to a file");

            setEnabled(false);
        }

        public void actionPerformed(ActionEvent arg0) {
            if (reportPanel != null) {
                reportPanel.getSaveReportAction().actionPerformed(null);
            }
        }
    }

    private class OperationsTableModel extends AbstractTableModel {
        public int getColumnCount() {
            return 4;
        }

        public int getRowCount() {
            return iface.getOperationCount();
        }

        @Override
        public String getColumnName(int column) {
            switch (column) {
                case 0:
                    return "Name";
                case 1:
                    return "Use";
                case 2:
                    return "One-Way";
                case 3:
                    return "Action";
            }

            return null;
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            if (updatingInterface) {
                return "<updating>";
            }

            WsdlOperation operation = iface.getOperationAt(rowIndex);
            BindingOperation bindingOperation = operation.getBindingOperation();

            switch (columnIndex) {
                case 0:
                    return operation.getName();
                case 1: {
                    boolean in = WsdlUtils.isInputSoapEncoded(bindingOperation);
                    boolean out = operation.isUnidirectional() ? false : WsdlUtils.isOutputSoapEncoded(bindingOperation);

                    if (out && in) {
                        return "SOAP Encoding";
                    }
                    if (!out && !in) {
                        return "Literal";
                    }

                    return "Mixed";
                }
                case 3:
                    return operation.getAction();
                case 2:
                    return Boolean.valueOf(operation.isUnidirectional());
            }

            return null;
        }
    }
}
